/*
 * SpeedoDyno - Speedometer and dynamometer application for Android 
 * Copyright 2013 Guilhem VALENTIN
 * 
 * This file is part of SpeedoDyno
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SpeedoDyno. If not, see <http://www.gnu.org/licenses/>.
 */
package com.gvalentin.speedodyno.data;

import android.location.Location;
import android.util.Log;

/**
 * Calculates the output power
 * @author guilhem
 */
public class CalculatorPower implements Calculator<Double> {
	private static final String TAG = CalculatorPower.class.getSimpleName();

	// The values will change of only this fraction of difference to the new values */
	private final static double CHANGE_RATE = 0.6d;
	
	/*
	 * Theory
	 * 
	 * Power is calculated using the following formula:
	 * P = [acceleration] + [elevation]  + [aero drag]     + [friction]
	 * P = M*v*a          + M*g*v*tan(i) + 1/2*rho*SCx*v^3 + M*g*f*v
	 * 
	 * - aero drag: always positive
	 * - friction: always positive
	 * - acceleration: positive if accelerating, negative if slowing down
	 * - elevation: positive if ascending, negative if descending
	 * 
	 * Measured values:
	 * - P: power (Watts)
	 * - v: speed (m/s)
	 * - a: acceleration (m/s^2) (= (v-v0)/(t-t0))
	 * - tan(i): tangent of incline angle (no unit)
	 * 
	 * Configuration values:
	 * - M: total mass (kg)
	 * - SCx: aerodynamic drag coefficient (m^2)
	 * - f: friction coefficient (no unit)
	 * 
	 * Constant values:
	 * - rho: air volumic mass (kg/m^3) (=1.2 at sea level at 20 degrees Celcius)
	 *   http://www.thermexcel.com/french/tables/massair.htm
	 * - g: gravity acceleration (m/s^2) (=9.81)
	 * 
	 * For French people:
	 * https://fr.wikipedia.org/wiki/Liste_de_formules_%C3%A9l%C3%A9mentaires_de_physique
	 */

	// Constant values
	private static final double g = 9.81f; // m/s^2
	private static final double rho = 1.2f; // kg/m^3

	
	// Current data
	private SpeedoDynoData data;

	// Configuration values
	/** Mass (kg) */
	private int M;
	/** Friction coefficient (no unit) */
	private double f;
	/** SCx aerodynamic coefficient (m^2) */
	private double SCx;
	
	// Differents values of power consumption
	private double powerAccel     = 0.0f;
	private double powerElevation = 0.0f;
	private double powerAero      = 0.0f;
	private double powerFriction  = 0.0f;
	private double power          = 0.0f;

	/**
	 * Constructor
	 */
	public CalculatorPower(SpeedoDynoData data, int M, double f, double SCx) {
		this.data = data;
		this.M = M;
		this.f = f;
		this.SCx = SCx;
	}

	/**
	 * Update the location. The new value for the power
	 * will be calculated based on the updated location
	 * @see com.gvalentin.speedodyno.data.Calculator#update(android.location.Location, android.location.Location)
	 */
	@Override
	public void update(Location previous, Location current) {
		if (current != null) {
			// Measured values
			double v = current.getSpeed(); // m/s^2
			double a = data.getAcceleration(); // m/s^2
			double tan_i = data.getInclinePer();
			
			// Raw calculated power
			double newPowerAccel = M*v*a;
			double newPowerElevation = M*g*v*tan_i;
			double newPowerAero = rho*SCx*Math.pow(v,3)/2;
			double newPowerFriction = M*g*f*v;
			
			// Use change rate to smooth value
			powerAccel     = applyChangeRate(powerAccel, newPowerAccel);
			powerElevation = applyChangeRate(powerElevation, newPowerElevation);
			powerAero      = applyChangeRate(powerAero, newPowerAero);
			powerFriction  = applyChangeRate(powerFriction, newPowerFriction);
			
			power = powerAccel + powerElevation + powerAero + powerFriction;
			
			Log.d(TAG, "power=" + (int) power + "W");
		}
	}
	
	/**
	 * Apply change rate to get the value
	 */
	public double applyChangeRate(double previousValue, double targetValue) {
		return previousValue + (targetValue - previousValue) * CHANGE_RATE;
	}

	/**
	 * Returns the calculated power
	 * @see com.gvalentin.speedodyno.data.Calculator#getData()
	 */
	@Override
	public Double getData() {
		return power;
	}

	/**
	 * Get the acceleration part of the power
	 */
	public double getPowerAccel() {
		return powerAccel;
	}

	/**
	 * Get the elevation part of the power
	 */
	public double getPowerElevation() {
		return powerElevation;
	}

	/**
	 * Get the aerodynamic part of the power
	 */
	public double getPowerAero() {
		return powerAero;
	}

	/**
	 * Get the friction part of the power
	 */
	public double getPowerFriction() {
		return powerFriction;
	}
}
